package com.smsc.headend.connector.builder.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.NumberUtil;
import com.google.common.base.Preconditions;
import com.smsc.headend.common.enums.errorcode.DeviceConnectErrorCode;
import com.smsc.headend.connector.bo.AssociationState;
import com.smsc.headend.connector.bo.UnwrappedPacket;
import com.smsc.headend.connector.builder.AssociationBuilder;
import com.smsc.headend.connector.builder.LoggerBuilder;
import com.smsc.headend.connector.exception.UDISDeviceConException;
import com.smsc.headend.connector.service.*;
import com.smsc.headend.connector.service.protocol.DLMSPacketCodecService;
import com.smsc.headend.connector.utils.Cosem46ByteUtil;
import com.smsc.headend.connector.utils.CosemXMLUtil;
import com.smsc.headend.connector.utils.LogFileNameUtil;
import com.smsc.headend.module.asset.dmo.DLMSConfigDMO;
import com.smsc.headend.module.asset.entity.ComEntity;
import com.smsc.headend.module.asset.entity.Meter;
import com.smsc.headend.module.asset.entity.PlcMeterStatus;
import com.smsc.headend.module.data.dmo.DeviceSAPandKeys;
import com.smsc.headend.module.protocol.cosem.util.ByteUtils;
import gurux.dlms.*;
import gurux.dlms.enums.Authentication;
import gurux.dlms.enums.Conformance;
import gurux.dlms.enums.ExceptionServiceError;
import gurux.dlms.enums.InterfaceType;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SignatureException;
import java.util.Arrays;

/**
 * @author wxh
 * @date 2020/11/30 10:47
 */
@Slf4j
public class LLSAssociationBuilder implements AssociationBuilder {
    @Autowired
    TerminalChannelService terminalChannelService;
    @Autowired
    DeviceConnectService deviceConnectService;
    @Autowired
    DLMSPacketCodecService packetCodecService;
    @Autowired
    FeignAssetManagementService assetManagementService;
    @Autowired
    DeviceStatusService deviceStatusService;
    @Autowired
    LogFileNameUtil logFileNameUtil;
    @Value("${device.association.connection-timeout:60}")
    String connectionTimeout;
    Boolean noSyncBlockReceived = true;
    Integer connectionTimeoutSec;
    private GXDLMSClient lowClient = new GXDLMSClient();

    public enum LLSState {
        snrmRequest,
        llsCreate
    }

    @Override
    public byte[] nextPacket(ComEntity com, AssociationState associationState) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
        Integer currentStep = associationState.getStepNumber();
        if (currentStep == null) {
            return getInitRequest(associationState);
        }
        if (currentStep == LLSState.snrmRequest.ordinal()) {
            associationState.setStepNumber(LLSState.llsCreate.ordinal());
            return associationState.getClient().aarqRequest()[0];
        }
        return null;
    }

    private byte[] getInitRequest(AssociationState associationState) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
        boolean isHdlc = associationState.getClient().getInterfaceType().equals(InterfaceType.HDLC);
        if (isHdlc) {
            associationState.setStepNumber(LLSState.snrmRequest.ordinal());
            return associationState.getClient().snrmRequest();
        }
        associationState.setStepNumber(LLSState.llsCreate.ordinal());
        return associationState.getClient().aarqRequest()[0];
    }

    @Override
    public Boolean acceptPacket(ComEntity terminal, byte[] byteReceive, AssociationState associationState) throws UDISDeviceConException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
        Integer currentStep = associationState.getStepNumber();
        Boolean hasNext = true;
        LLSState state = Arrays.stream(LLSState.values()).filter(s -> s.ordinal() == currentStep).findFirst().get();
        switch (state) {
            case snrmRequest:
                snrmResponse(byteReceive, associationState);
                break;
            case llsCreate:
                llsAssociationResponse(terminal, byteReceive, associationState);
                hasNext = false;
                break;
        }
        return hasNext;
    }

    private void llsAssociationResponse(ComEntity terminal, byte[] bytes, AssociationState aaState) throws UDISDeviceConException {
        UnwrappedPacket unwrappedPacket = packetCodecService.unwrapPrefixPacket(bytes);
        GXByteBuffer buffer = new GXByteBuffer();
        GXReplyData gxReplyData = new GXReplyData();
        buffer.set(unwrappedPacket.getPacket());
        String errorMessage = null;
        try {
            aaState.getClient().getData(buffer, gxReplyData);
            aaState.getClient().parseAareResponse(gxReplyData.getData());
            String pduData = CosemXMLUtil.translator.pduToXml(gxReplyData.getData().toString());
            if (!"00".equalsIgnoreCase(CosemXMLUtil.getAssociationResult(pduData))) {
                throw new UDISDeviceConException(DeviceConnectErrorCode.AARE_AUTH_FAIL.getErrorText());
            }
            if (aaState.getClient().getInterfaceType().equals(InterfaceType.HDLC)) {
                // 塞入frameType
                aaState.setFrameId(Cosem46ByteUtil.getFrameType(aaState.getClient(), unwrappedPacket.getPacket()));
            }
            LoggerBuilder.getLogger(logFileNameUtil.getLogFileName(terminal), this.getClass()).info("get lowest secret association success");
//            deviceStatusService.updateDeviceStatus(terminal, meter);
//            deviceStatusService.updateComEntityStatus(terminal, ComEntity.CommunicationStatus.ONLINE.getCode());
//            deviceStatusService.updatePlcMeterStatus(meter, PlcMeterStatus.Status.Online.getCode());
        } catch (GXDLMSException e) {
            errorMessage = e.getMessage();
        } catch (GXDLMSExceptionResponse e) {
            if (noSyncBlockReceived && ExceptionServiceError.OPERATION_NOT_POSSIBLE.equals(e.getExceptionServiceError())) {
                //第一次返回D8 01 01
                return;
            }
            errorMessage = e.getMessage();
        } catch (Exception e) {
            log.error("aa unknown error,bytes:{}", ByteUtils.byteToHexString(bytes), e);
            errorMessage = "build aa failed";
        } catch (UDISDeviceConException e) {
            e.printStackTrace();
        }
        if (StringUtils.isNotEmpty(errorMessage)) {
            throw new UDISDeviceConException(errorMessage);
        }
    }

    private void snrmResponse(byte[] aare, AssociationState associationState) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
        UnwrappedPacket packet = packetCodecService.unwrapPrefixPacket(aare);
        GXReplyData replyData = new GXReplyData();
        GXByteBuffer gxByteBuffer = new GXByteBuffer();
        gxByteBuffer.set(packet.getPacket());
        associationState.getClient().getData(gxByteBuffer, replyData);
        associationState.getClient().parseUAResponse(replyData.getData());
    }

    @Override
    public void buildAssociation(ComEntity terminal, Meter meter, Channel channel, AssociationState aaState) throws NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, InterruptedException, UDISDeviceConException, SignatureException {
        this.connectionTimeoutSec = NumberUtil.isNumber(connectionTimeout) ? Integer.parseInt(connectionTimeout) : 60;
        DLMSConfigDMO dlmsConfigDMO = packetCodecService.getDLMSConfigDMO(terminal, meter);
        if (dlmsConfigDMO != null && dlmsConfigDMO.getInterfaceType().equalsIgnoreCase(InterfaceType.HDLC.name())) {
            // 46 HDLC需要发送snrm request
            sendSnrmRequest(terminal, meter, channel, aaState);
        }
        Preconditions.checkNotNull(dlmsConfigDMO);
        createAssociation(terminal, meter, channel, dlmsConfigDMO, aaState);
    }

    public void sendSnrmRequest(ComEntity terminal, Meter meter, Channel channel, AssociationState aaState) throws NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, UDISDeviceConException, InterruptedException {
        byte[] aarq = aaState.getClient().snrmRequest();

        String deviceAddress = meter == null ? null : meter.getMfgSn();
        aarq = packetCodecService.wrapPrefixToPacket(aarq, terminal, deviceAddress);
        deviceConnectService.sendPacket2Channel(channel, aarq);
        //wait 60 s for aare
        byte[] aare = terminalChannelService.getSyncResponse(channel, this.connectionTimeoutSec, aaState.getDeviceAddress());
        if (aare != null && aare.length > 0) {
            noSyncBlockReceived = false;
            UnwrappedPacket packet = packetCodecService.unwrapPrefixPacket(aare);
            GXReplyData replyData = new GXReplyData();
            GXByteBuffer gxByteBuffer = new GXByteBuffer();
            gxByteBuffer.set(packet.getPacket());
            aaState.getClient().getData(gxByteBuffer, replyData);
            aaState.getClient().parseUAResponse(replyData.getData());
        } else {
            throw new UDISDeviceConException("build snrm request fail");
        }
    }

    private void createAssociation(ComEntity terminal, Meter meter, Channel channel, DLMSConfigDMO dlmsConfigDMO, AssociationState aaState) throws NoSuchPaddingException, InvalidAlgorithmParameterException,
            NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, InterruptedException, UDISDeviceConException {
        lowClient.setServerAddress(aaState.getClient().getServerAddress());
        lowClient.setClientAddress(aaState.getClient().getClientAddress());
        lowClient.setInterfaceType(InterfaceType.valueOf(dlmsConfigDMO.getInterfaceType()));
        lowClient.setUseLogicalNameReferencing(!dlmsConfigDMO.getIsShortName());
        lowClient.setMaxReceivePDUSize((dlmsConfigDMO.getMaxPduSize() == null ? 1024 : dlmsConfigDMO.getMaxPduSize()));
        lowClient.setProposedConformance(CollUtil.newHashSet(Conformance.getEnumConstants()));
        DeviceSAPandKeys deviceConnectKeys = assetManagementService.getMeterCommunicationKey(meter.getMeterId(), aaState.getRoleId());
        if (null != deviceConnectKeys && StringUtils.isNotEmpty(deviceConnectKeys.getLlsKey())) {
            lowClient.setPassword(deviceConnectKeys.getLlsKey().getBytes(StandardCharsets.UTF_8));
        }
        lowClient.setAuthentication(Authentication.LOW);
        byte[] aa = lowClient.aarqRequest()[0];
        aa = packetCodecService.wrapPrefixToPacket(aa, terminal, aaState.getDeviceAddress());
        deviceConnectService.sendPacket2Channel(channel, aa);
        byte[] bytes = terminalChannelService.getSyncResponse(channel, this.connectionTimeoutSec, aaState.getDeviceAddress());
        if (bytes == null) {
            throw new UDISDeviceConException("build LLS low aa fail");
        }
        UnwrappedPacket unwrappedPacket = packetCodecService.unwrapPrefixPacket(bytes);
        GXByteBuffer buffer = new GXByteBuffer();
        GXReplyData gxReplyData = new GXReplyData();
        buffer.set(unwrappedPacket.getPacket());
        String errorMessage = null;
        try {
            lowClient.getData(buffer, gxReplyData);
            lowClient.parseAareResponse(gxReplyData.getData());
            String pduData = CosemXMLUtil.translator.pduToXml(gxReplyData.getData().toString());
            if (!"00".equalsIgnoreCase(CosemXMLUtil.getAssociationResult(pduData))) {
                throw new UDISDeviceConException(DeviceConnectErrorCode.AARE_AUTH_FAIL.getErrorText());
            }
            if (aaState.getClient().getInterfaceType().equals(InterfaceType.HDLC)) {
                // 塞入frameType
                aaState.setFrameId(Cosem46ByteUtil.getFrameType(aaState.getClient(), unwrappedPacket.getPacket()));
            }
            LoggerBuilder.getLogger(logFileNameUtil.getLogFileName(terminal), this.getClass()).info("get lowest secret association success");
        } catch (GXDLMSException e) {
            errorMessage = e.getMessage();
        } catch (GXDLMSExceptionResponse e) {
            if (noSyncBlockReceived && ExceptionServiceError.OPERATION_NOT_POSSIBLE.equals(e.getExceptionServiceError())) {
                //第一次返回D8 01 01
                return;
            }
            errorMessage = e.getMessage();
        } catch (Exception e) {
            log.error("aa unknown error", e);
            errorMessage = "build aa failed";
        }
        if (StringUtils.isNotEmpty(errorMessage)) {
            throw new UDISDeviceConException(errorMessage);
        }
    }


}
